/*
 * Copyright (c) 2006-2020, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-05-21     chenhy       the first version
 */
#include "i2c.h"
#include "tim.h"

#define I2C_REQUEST_WRITE     0x00
#define I2C_REQUEST_READ      0x01
#define I2C_DELAY_TIMER       htim7

bus_io_t i2c1 = {
  .scl_port = (uint32_t)GPIOB,
  .scl_pin = (uint32_t)GPIO_PIN_8,
  .sda_port = (uint32_t)GPIOB,
  .sda_pin = (uint32_t)GPIO_PIN_9
};

bus_io_t i2c2 = {
  .scl_port = (uint32_t)GPIOB,
  .scl_pin = (uint32_t)GPIO_PIN_10,
  .sda_port = (uint32_t)GPIOB,
  .sda_pin = (uint32_t)GPIO_PIN_11
};

bus_io_t *bus_i2c;

static void i2c_delay(void)
{
  TIM_HandleTypeDef *htim;
  htim = &I2C_DELAY_TIMER;
  HAL_TIM_GenerateEvent(htim, TIM_EVENTSOURCE_UPDATE);
  __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
  while(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) == RESET);
  __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
}

void i2c_delay_ms(int32_t value)
{
  int32_t delay_cnt;
  delay_cnt = 300 * value;
  while(delay_cnt--) {
    i2c_delay();
  }
}

static void i2c_sda_out(void)
{
  GPIO_InitTypeDef  gpio_cfg;
  __HAL_RCC_GPIOB_CLK_ENABLE();
  gpio_cfg.Pin = bus_i2c->sda_pin;
  gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
  gpio_cfg.Pull = GPIO_PULLUP;
  gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}

static void i2c_sda_write(uint8_t value)
{
  HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin, value);
}

static void i2c_sda_in(void)
{
  GPIO_InitTypeDef  gpio_cfg;
  __HAL_RCC_GPIOB_CLK_ENABLE();
  gpio_cfg.Pin = bus_i2c->sda_pin;
  gpio_cfg.Mode = GPIO_MODE_INPUT;
  gpio_cfg.Pull = GPIO_PULLUP;
  HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->sda_port, &gpio_cfg);
}

static uint32_t i2c_sda_read(void)
{
  return HAL_GPIO_ReadPin((GPIO_TypeDef*)bus_i2c->sda_port, bus_i2c->sda_pin);
}

static void i2c_scl_out(void)
{
  GPIO_InitTypeDef  gpio_cfg;
  __HAL_RCC_GPIOB_CLK_ENABLE();
  gpio_cfg.Pin = bus_i2c->scl_pin;
  gpio_cfg.Mode = GPIO_MODE_OUTPUT_OD;
  gpio_cfg.Pull = GPIO_PULLUP;
  gpio_cfg.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init((GPIO_TypeDef*)bus_i2c->scl_port, &gpio_cfg);
}

static void i2c_scl_write(uint8_t value)
{
  HAL_GPIO_WritePin((GPIO_TypeDef*)bus_i2c->scl_port, bus_i2c->scl_pin, value);
}

static void i2c_start(void)
{
  i2c_sda_out();
  i2c_sda_write(1);
  i2c_delay();
  i2c_scl_write(1);
  i2c_delay();
  i2c_sda_write(0);
  i2c_delay();
  i2c_scl_write(0);
  i2c_delay();
}

static void i2c_stop(void)
{
  i2c_sda_out();
  i2c_sda_write(0);
  i2c_delay();
  i2c_scl_write(1);
  i2c_delay();
  i2c_sda_write(1);
  i2c_delay();
}

static void i2c_write_nack(void)
{
  i2c_sda_out();
  i2c_sda_write(1);
  i2c_delay();
  i2c_scl_write(1);
  i2c_delay();
  i2c_scl_write(0);
  i2c_delay();
}

static void i2c_write_ack(void)
{
  i2c_sda_out();
  i2c_sda_write(0);
  i2c_delay();
  i2c_scl_write(1);
  i2c_delay();
  i2c_scl_write(0);
  i2c_delay();
}

static uint8_t i2c_read_ack(void)
{
  uint8_t level = 0;
  i2c_sda_in();
  i2c_scl_write(1);
  i2c_delay();
  if(i2c_sda_read())
    level = 1;
  i2c_scl_write(0);
  i2c_delay();
  return level;
}

static void i2c_write_byte(uint16_t data)
{
  int i;
  uint8_t temp = (uint8_t)(data & 0xFF);
  i2c_sda_out();
  for(i=0;i<8;i++)
  {
    if(temp & 0x80)
      i2c_sda_write(1);
    else
      i2c_sda_write(0);
    temp <<= 1;
    i2c_delay();
    i2c_scl_write(1);
    i2c_delay();
    i2c_scl_write(0);
    i2c_delay();
  }
}

static uint8_t i2c_read_byte(void)
{
  int i;
  uint8_t temp = 0;
  i2c_sda_in();
  for(i=0;i<8;i++)
  {
    temp <<= 1;
    if(i2c_sda_read())
      temp |= 0x01;
    i2c_delay();
    i2c_scl_write(1);
    i2c_delay();
    i2c_scl_write(0);
    i2c_delay();
  }
  return temp;
}

int32_t i2c_write(bus_io_t* bus, uint8_t address, uint16_t reg, void* pbuf, int32_t size, uint8_t reg_16bit)
{
  int32_t i = 0;
  uint8_t* buf_ptr;
  if(pbuf == NULL) return i;
  buf_ptr = (uint8_t*)pbuf;
  bus_i2c = bus;
  i2c_scl_out();
  i2c_start();
  i2c_write_byte(address | I2C_REQUEST_WRITE);
  if(i2c_read_ack()) {
    i2c_stop();
    return -1;
  }
  if(reg_16bit) {
    i2c_write_byte(reg >> 8);
    if(i2c_read_ack()) {
      i2c_stop();
      return -2;
    }
  }
  i2c_write_byte(reg);
  if(i2c_read_ack()) {
    i2c_stop();
    return -3;
  }
  for(i=0;i<size;i++) {
    i2c_write_byte(buf_ptr[i]);
    if(i2c_read_ack()) {
      i2c_stop();
      return i;
    }
  }
  i2c_stop();
  return i;
}

int32_t i2c_read(bus_io_t* bus, uint8_t address, uint16_t reg, void* pbuf, int32_t size, uint8_t reg_16bit)
{
  int32_t i = 0;
  uint8_t* buf_ptr;
  if(pbuf == NULL) return i;
  buf_ptr = (uint8_t*)pbuf;
  bus_i2c = bus;
  i2c_scl_out();
  i2c_start();
  i2c_write_byte(address | I2C_REQUEST_WRITE);
  if(i2c_read_ack()) {
    i2c_stop();
    return -1;
  }
  if(reg_16bit) {
    i2c_write_byte(reg >> 8);
    if(i2c_read_ack()) {
      i2c_stop();
      return -2;
    }
  }
  i2c_write_byte(reg);
  if(i2c_read_ack()) {
    i2c_stop();
    return -3;
  }
  i2c_start();
  i2c_write_byte(address | I2C_REQUEST_READ);
  if(i2c_read_ack()) {
    i2c_stop();
    return -4;
  }
  for(i=0;i<size;i++) {
    buf_ptr[i] = i2c_read_byte();
    if(i == size - 1)
      i2c_write_nack();
    else
      i2c_write_ack();
  }
  i2c_stop();
  return i;
}

int32_t i2c_bytes_write(bus_io_t* bus, uint8_t address, void* pbuf, int32_t size)
{
  int32_t i = 0;
  uint8_t* buf_ptr;
  if(pbuf == NULL) return i;
  buf_ptr = (uint8_t*)pbuf;
  bus_i2c = bus;
  i2c_scl_out();
  i2c_start();
  i2c_write_byte(address | I2C_REQUEST_WRITE);
  if(i2c_read_ack()) {
    i2c_stop();
    return i;
  }
  for(i=0;i<size;i++) {
    i2c_write_byte(buf_ptr[i]);
    if(i2c_read_ack()) {
        i2c_stop();
        return i;
    }
  }
  i2c_stop();
  return SUCCESS;
}

int32_t i2c_bytes_read(bus_io_t* bus, uint8_t address, void* pbuf, int32_t size)
{
  int32_t i = 0;
  uint8_t* buf_ptr;
  if(pbuf == NULL) return i;
  buf_ptr = (uint8_t*)pbuf;
  bus_i2c = bus;
  i2c_scl_out();
  i2c_start();
  i2c_write_byte(address | I2C_REQUEST_READ);
  if(i2c_read_ack()) {
    i2c_stop();
    return i;
  }
  for(i=0;i<size;i++) {
    buf_ptr[i] = i2c_read_byte();
    if(i == size - 1)
      i2c_write_nack();
    else
      i2c_write_ack();
  }
  i2c_stop();
  return i;
}
